listview: Implement extending selections
authorBenjamin Otte <otte@redhat.com>
Sun, 6 Oct 2019 02:10:09 +0000 (04:10 +0200)
committerMatthias Clasen <mclasen@redhat.com>
Sat, 30 May 2020 23:26:45 +0000 (19:26 -0400)
Shift-clicking to extend selections now also works, imitating the
behavior of normal clicking and Windows Explorer (but not treeview):

1. We track the last selected item (normally, not via extend-clicking).

2. When shift-selecting, we modify the range from the last selected item
   to this item the same way we modify the regular item when not using
   shift:

2a. If Ctrl is not pressed, we select the range and unselect everything
    else.

2b. If Ctrl is pressed, we make the range have the same selection state
    as the last selected item:
    - If the last selected item is selected, select the range.
    - If the last selected item is not selected, unselect the range.

gtk/gtklistview.c

index 6d0283139d1fc2862b4e3c8a9e1792fd7759c0d2..6f0c314f11a00e5c6cc7e2fd1a00e7c78d8d071b 100644 (file)
@@ -68,6 +68,8 @@ struct _GtkListView
 
   GtkListItemTracker *anchor;
   double anchor_align;
+  /* the last item that was selected - basically the location to extend selections from */
+  GtkListItemTracker *selected;
 };
 
 struct _ListRow
@@ -567,6 +569,11 @@ gtk_list_view_dispose (GObject *object)
       gtk_list_item_tracker_free (self->item_manager, self->anchor);
       self->anchor = NULL;
     }
+  if (self->selected)
+    {
+      gtk_list_item_tracker_free (self->item_manager, self->selected);
+      self->selected = NULL;
+    }
   g_clear_object (&self->item_manager);
 
   G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object);
@@ -712,22 +719,66 @@ gtk_list_view_select_item (GtkWidget  *widget,
   GtkSelectionModel *selection_model;
   guint pos;
   gboolean modify, extend;
+  gboolean success = FALSE;
 
   selection_model = gtk_list_item_manager_get_model (self->item_manager);
   g_variant_get (parameter, "(ubb)", &pos, &modify, &extend);
 
-  /* XXX: handle extend by tracking the item to extend from */
+  if (extend)
+    {
+      guint start_pos = gtk_list_item_tracker_get_position (self->item_manager, self->selected);
+      if (start_pos != GTK_INVALID_LIST_POSITION)
+        {
+          guint max = MAX (start_pos, pos);
+          guint min = MIN (start_pos, pos);
+          if (modify)
+            {
+              if (gtk_selection_model_is_selected (selection_model, start_pos))
+                {
+                  success = gtk_selection_model_select_range (selection_model,
+                                                              min,
+                                                              max - min + 1,
+                                                              FALSE);
+                }
+              else
+                {
+                  success = gtk_selection_model_unselect_range (selection_model,
+                                                                min,
+                                                                max - min + 1);
+                }
+            }
+          else
+            {
+              success = gtk_selection_model_select_range (selection_model,
+                                                          min,
+                                                          max - min + 1,
+                                                          TRUE);
+            }
+        }
+      /* If there's no range to select or selecting ranges isn't supported
+       * by the model, fall through to normal setting.
+       */
+    }
+  if (success)
+    return;
 
   if (modify)
     {
       if (gtk_selection_model_is_selected (selection_model, pos))
-        gtk_selection_model_unselect_item (selection_model, pos);
+        success = gtk_selection_model_unselect_item (selection_model, pos);
       else
-        gtk_selection_model_select_item (selection_model, pos, FALSE);
+        success = gtk_selection_model_select_item (selection_model, pos, FALSE);
     }
   else
     {
-      gtk_selection_model_select_item (selection_model, pos, TRUE);
+      success = gtk_selection_model_select_item (selection_model, pos, TRUE);
+    }
+  if (success)
+    {
+      gtk_list_item_tracker_set_position (self->item_manager,
+                                          self->selected,
+                                          pos,
+                                          0, 0);
     }
 }
 
@@ -872,6 +923,7 @@ gtk_list_view_init (GtkListView *self)
 {
   self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self), ListRow, ListRowAugment, list_row_augment);
   self->anchor = gtk_list_item_tracker_new (self->item_manager);
+  self->selected = gtk_list_item_tracker_new (self->item_manager);
 
   self->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
   self->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);